### -*- coding: utf-8 -*- #############################################
# Разработано Паэглит Арвидом (http://arvid.dp.ua/)
# Все права защищены, 2007
#
# Developed by Paeglit Arvid (http://arvid.dp.ua/)
# All right reserved, 2007
#######################################################################
"""Genetic class for the Zope 3 based diplom.variant package

$Id: genetic.py 75 2008-03-13 01:42:51Z Arvi3D $
"""
__author__  = "Arvid"
__license__ = "GPL"
__version__ = "$Revision: 75 $"
__date__ = "$Date: 2008-03-13 01:42:51 +0000 (Thu, 13 Mar 2008) $"

from persistent import Persistent
from zope.app.container.contained import Contained
from zope.schema.fieldproperty import FieldProperty
from zope.interface import implements,implementedBy
from zope.security.proxy import removeSecurityProxy

from random import random, expovariate
from math import sqrt, exp

from diplom.ga.interfaces import IChromosomeFactory
from diplom.interfaces import IGenetic

class Genetic(Persistent) :
    implements(IGenetic)

    #Генетический алгоритм
    ga = ()

    populationCount = FieldProperty(IGenetic['populationCount'])
    crossover = FieldProperty(IGenetic['crossover'])
    mutation = FieldProperty(IGenetic['mutation'])
    generation = FieldProperty(IGenetic['generation'])

    @property
    def best(self):
        return self.ga and str(self.ga) or ''

    eval = 0.0

    def runAlgorithm(self, apparat):
        """find optimum and save in attribute ga"""
        #Популяция
        population = {}

        factory = IChromosomeFactory(apparat)

        #Функция приспособленности
        adjusted = lambda v : exp(factory.eval(v))

        #Генерируем начальную популяцию
        for i in range(self.populationCount):
            chromosome = factory.generate()
            population[adjusted(chromosome)] = chromosome

        for gen in range(self.generation):
            #Запомним численность текущей популяции
            count = len(population)

            #population.sort(key=lambda r: factory.eval(r), reverse = True)
            #print "best --> ", factory.params(population[0])
            #print "eval(best) --> ", factory.eval(population[0])

            evalAll = 0
            intervals = []
            for adj, item in population.items():
                evalAll += adj
                intervals.append((evalAll, item))

            #print "Начало размножения"
            nextgen = {}
            for i in range(count/2):
                parents = []

                if count == 2:
                    parents = population[:]
                else:
                    #Первый родитель
                    interval = random()*evalAll
                    for it, chrom in intervals:
                        if interval <= it:
                            parents.append(chrom[:])
                            break

                    #Второй родитель
                    while(True):
                        interval = random()*evalAll
                        for it, chrom in intervals:
                            if interval <= it:
                                if parents[0] != chrom:
                                    parents.append(chrom[:])
                                break
                        if len(parents) == 2:
                            break
                #print "Calculated parents"

                changed = False
                #Скрестить
                childs = []
                if random() <= self.crossover:
                    item = int(random()*(factory.n - 1))
                    childs.append(parents[0][:item] + parents[1][item:])
                    childs.append(parents[1][:item] + parents[0][item:])
                    changed = True
                else:
                    childs = parents

                if random() <= self.mutation:
                    for child in childs:
                        item = int(random()*(factory.n - 1))
                        childs[0] = child[:item] + [int(not child[item])] + child[item + 1:]
                    changed = True
                #print "Calculated childs "
                if changed:
                    nextgen.update(dict([(adjusted(c), c) for c in childs]))

            population.update(nextgen)

            #Отбор в случае превышения численности
            while(len(population) > self.populationCount):
                del population[min(population)]


        print "BEST --> ", factory.params(population[max(population)])
        print "eval(BEST) --> ", max(population)

        self.ga = removeSecurityProxy(factory.params(population[max(population)]))
        self.eval = removeSecurityProxy(max(population))
